﻿using System;
using System.IO;
using System.Text;
using System.Timers;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;


namespace Shuriken.Core.Ranking
{
    public class PopularityRankProvider : IRankProvider
    {
        QSRankProvider qsRank = new QSRankProvider();
        #region IRankProvider Members

        public double CalculateRank(Shuriken.Core.Store.ISearchable candidate, string expression, MutableIndexSet hitMask)
        {
            double rank = qsRank.CalculateRank(candidate.Name, expression, hitMask);
            double popularity = 0; 

            if (rank > 0)
            {
                popularity = PopularityStore.DefaultInstance.CalculatePopularity(candidate.Name, expression);
                return (0.4 * popularity) + (0.6 * rank);  
            }

            return rank; 
        }

        public void RegisterHit(Shuriken.Core.Store.ISearchable candidate)
        {
            if (!string.IsNullOrEmpty(candidate.SearchTerm))
            {
                PopularityStore.DefaultInstance.IncreasePopularity(candidate.Name, candidate.SearchTerm);
                // PopularityStore.DefaultInstance.Save();
            }
        }

        #endregion
    }

    [Serializable]
    public class PopularityEntry
    {
        private string firstChars = string.Empty;

        public DateTime LastHit { get; set; }
        public string FirstCharacters { get { return firstChars; } }
        public double Hits { get; set; }

        public bool DoesCharExist(char c)
        {
            return firstChars.ToLower().Contains(c.ToString().ToLower()); 
        }

        public void AddFirstChar(char c)
        {
            if (!DoesCharExist(c))
            {
                firstChars += c.ToString().ToLower();
            }
        }

        public void AddFirstChar(string str)
        {
            if (!string.IsNullOrEmpty(str))
            {
                AddFirstChar(str.ToLower()[0]);
            }
        }

        public void RemoveFirstChar(char c)
        {
            if (DoesCharExist(c))
            {
                firstChars = firstChars.ToLower().Replace(c.ToString().ToLower(), "");
            }
        }

        public void RemoveFirstChar(string str)
        {
            if (!string.IsNullOrEmpty(str))
            {
                RemoveFirstChar(str.ToLower()[0]);
            }
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("PopularityEntry{LastHit=");
            sb.Append(this.LastHit);
            sb.Append(",Hits=");
            sb.Append(this.Hits);
            sb.Append(",FirstChars=");
            sb.Append(this.firstChars);
            sb.Append("}");

            return sb.ToString();
        }
    }

    public class PopularityStore
    {
        private Dictionary<string, PopularityEntry> popularity;
        private DateTime oldest_date;
        private double max_hits;
        private Timer serialization_timer;
        private const int serialization_interval_minutes = 10;
        private bool dirty; 

        private static PopularityStore _defaultInstance; 

        public PopularityStore()
        {
            popularity = new Dictionary<string,PopularityEntry>();
            oldest_date = DateTime.Now;
            max_hits = 0;
            dirty = false; 
        }

        public static PopularityStore DefaultInstance
        {
            get
            {
                if (_defaultInstance == null)
                {
                    _defaultInstance = Load();
                    _defaultInstance.InitalizeTimer();
                }

                return _defaultInstance; 
            }
        }

        /// <summary>
        /// Initializes timer for self-serializing 
        /// </summary>
        private void InitalizeTimer()
        {
            serialization_timer = new Timer();
            serialization_timer.Elapsed += new ElapsedEventHandler(serialization_timer_Elapsed);

            // In every 10 minutes the store is saved. 
            serialization_timer.Interval = 10 * 60 * 1000;
            serialization_timer.Enabled = true; 
        }

        void serialization_timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (dirty)
            {
                Save();
                dirty = false; 
            }
        }


        public static string FileName
        {
            get
            {
                return ApplicationEnvironment.LocalApplicationDataDirectory + "\\popularity";
            }
        }

        protected void Deserialize()
        {
            try
            {

                if (!File.Exists(PopularityStore.FileName))
                {
                    return;
                }

                using (Stream s = File.OpenRead(PopularityStore.FileName))
                {
                    BinaryFormatter bin = new BinaryFormatter();
                    popularity = (Dictionary<string, PopularityEntry>)bin.Deserialize(s);
                }

                foreach (KeyValuePair<string, PopularityEntry> entry in popularity)
                {
                    max_hits = (max_hits < entry.Value.Hits) ? entry.Value.Hits : max_hits;
                    oldest_date = (oldest_date > entry.Value.LastHit) ? entry.Value.LastHit : oldest_date;
                }
            }
            catch (Exception excp)
            {
                string msg = ApplicationEnvironment.Diagnostics.FormatCriticalError(excp);
                Shuriken.Core.Utils.Tracer.WriteError(msg, "Failed to load popularity store!");                
            }
            finally 
            {
                Shuriken.Core.Utils.Tracer.WriteWarning("Successfully load popularity store!");                
            }
        }

        public void Save()
        {
            try
            {
                using (Stream s = File.OpenWrite(PopularityStore.FileName))
                {
                    BinaryFormatter bin = new BinaryFormatter();
                    bin.Serialize(s, popularity);
                }
            }
            catch (Exception excp)
            {
                string msg = ApplicationEnvironment.Diagnostics.FormatCriticalError(excp);
                Shuriken.Core.Utils.Tracer.WriteError(msg, "Failed to save popularity store!");                
            }
            finally
            {
                Shuriken.Core.Utils.Tracer.WriteWarning("Successfully save popularity store!");                
            }
        }

        public static PopularityStore Load()
        {
            PopularityStore pop = new PopularityStore();
            pop.Deserialize();
            return pop; 
        }

        public bool InPopularityStore(string key)
        {
            return popularity.ContainsKey(key); 
        }

        public void IncreasePopularity(string key, string expression)
        {
            if (InPopularityStore(key))
            {
                PopularityEntry entry = popularity[key];
                entry.LastHit = DateTime.Now;
                entry.Hits++;
                entry.AddFirstChar(expression);
            }
            else
            {
                PopularityEntry entry = new PopularityEntry();
                entry.LastHit = DateTime.Now;
                entry.Hits++;

                if(!string.IsNullOrEmpty(expression))
                {
                    entry.AddFirstChar(expression);
                }

                popularity.Add(key, entry);
            }

            max_hits++;
            dirty = true; 
        }

        public double CalculatePopularity(string key, string expression)
        {
            double popular = 0;

            if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(expression) || !InPopularityStore(key))
            {
                return 0; 
            }

            PopularityEntry entry = popularity[key];                       

            if (!entry.DoesCharExist(expression.ToLower()[0]))
            {
                return 0; 
            }

            double hit_value = entry.Hits / max_hits;
            double age = (double)((DateTime.Now - entry.LastHit).TotalSeconds / (DateTime.Now - oldest_date).TotalSeconds);

            popular = (double)(hit_value * 0.5f * (1f + age)); 

            return popular; 
        }

    }
}
